import axios from 'axios'
import qs from 'qs'
import { Message, MessageBox } from 'element-ui'
import { getToken, loginConfirm, UnAuthError } from '@/util/auth'
import store from '@/store'
import { frontendDownload, error, isString, isObject, isArray } from '@web-utils/core'
import { getStore } from '@/util/store'
import { withContext } from '@/util/core'

let tipsShowing = false
const formContentType = 'application/x-www-form-urlencoded; charset=UTF-8'

// 创建统一配置的axios实例
export const http = axios.create({
  timeout: 3 * 60 * 1000,
  transformRequest(data, headers) {
    if (isObject(data) || isArray(data)) {
      if (headers['Content-Type'] === formContentType) {
        return qs.stringify(data, { arrayFormat: 'comma' })
      }
      try {
        const transformData = JSON.stringify(data)
        headers['Content-Type'] = 'application/json;charset=utf-8'
        return transformData
      } catch (e) {
        error(e)
      }
    }
    return data
  },
  transformResponse(data) {
    if (data && isString(data)) {
      try {
        return JSON.parse(data)
      } catch (e) {
        // console.log(data)
        error(e)
        return data
      }
    }
    return data
  },
  paramsSerializer: {
    serialize(params) {
      return qs.stringify(params, { arrayFormat: 'comma' })
    }
  }
})

window.axios = http

/**
 *
 * @type {axios.AxiosInstance}
 */
export const request = http

const setContentType = (headers, isForm) => {
  if (isForm) {
    headers['Content-Type'] = formContentType
  }
  return headers
}

// 发起请求之前
http.interceptors.request.use(
  // 成功回调
  config => {
    const headers = defaultHeaders()
    if (config.url.indexOf('http') === -1) {
      config.url = withContext(config.url)
    }
    Object.keys(headers).forEach(key => {
      if (key === 'Authorization') {
        config.headers[key] = config.headers[key] || headers[key]
      } else {
        config.headers[key] = headers[key]
      }
    })
    return process.env.VUE_APP_OFFLINE !== 'Y' ? config : Promise.reject(config)
  },
  // 出错回调
  error => Promise.reject(error)
)

// 请求完成之后
http.interceptors.response.use(
  response => {
    // 没有发生网络错误且请求响应码 -> status >= 200 && status < 300
    if (['text', 'blob'].includes(response.config.responseType)) {
      return Promise.resolve(response)
    } else if (response.config.common) {
      return Promise.resolve(response)
    } else if (response.data.code === 1) {
      Message({ message: response.data.msg, type: 'error' })
      return Promise.reject(response.data)
    } else if (
      response.data.code >= 0 ||
      response.config.url.indexOf('auth/oauth/token') > -1 ||
      response.config.url.indexOf('admin/user/deleteRedisUser') > -1
    ) {
      // 服务端定义的响应 code 大于等于 0 时请求成功
      return Promise.resolve(response)
    } else if (response.data.code === -10000) {
      MessageBox.alert('证书无效,继续使用请重新申请授权证书！', {
        closeOnHashChange: false,
        type: 'warning',
        showClose: false,
        showConfirmButton: false
      }).then()
      return Promise.reject(response.data)
    } else {
      Message({ message: response.data.msg, type: 'error' })
      return Promise.reject(response.data)
    }
  },
  async error => {
    // 发生网络错误或响应码不在设定范围内
    if (error && error.response) {
      // 响应码不在设定范围内
      let message = ''
      const { status, data } = error.response
      switch (status) {
        case 401:
        case 424:
          if (error.config.url.indexOf('auth/logout/token') > -1) {
            return Promise.resolve({ message: 'success' })
          } else {
            const authError = new UnAuthError(data?.msg || error.message, error.config.url)
            return invalidToken(authError)
          }
        case 403:
          message = '无权限！'
          break
        case 429:
          message = '访问太过频繁，请稍后再试！'
          break
        case 404:
          message = '找不到服务，请稍后再试！'
          break
        default:
          message = error.response.data?.msg ?? '服务器错误'
          break
      }
      return showErrorTips(error, message)
    } else if (error && error.config) {
      // 发生网络错误
      if (error.config.url.indexOf('auth/current/user') > -1 && !error.config.headers.Authorization) {
        // 获取当前用户信息且 token 为空直接跳转到登录页面
        return invalidToken(error)
      } else if (error.config.url.indexOf('auth/current/user') > -1) {
        // 获取当前用户信息
        return showErrorTips(
          error,
          '连接服务器失败，请刷新页面重试。如刷新多次后仍然无法连接，请检查网络或联系管理员！',
          true
        )
      } else if (error.config.url.indexOf('auth/login/token') > -1) {
        // 登录
        return showErrorTips(error, '服务不可用，请检查网络或联系管理员！', true)
      } else {
        // 其他网络请求
        return showErrorTips(error, '连接服务器失败,请检查网络或联系管理员！')
      }
    } else if (error) {
      return Promise.reject(error)
    } else {
      // 离线模式
      return Promise.reject(new Error('当前处于离线模式！'))
    }
  }
)

/**
 * 显示错误提示信息,防止连续弹出提示
 * @param error Error对象
 * @param message 错误提示信息
 * @param keep 是否保持错误信息一直显示
 * @returns {Promise<Error>}
 */
function showErrorTips(error, message, keep = false) {
  if (!tipsShowing) {
    tipsShowing = true
    Message({
      message,
      duration: keep ? 0 : 3000,
      showClose: keep,
      type: 'error',
      onClose: () => {
        tipsShowing = false
      }
    })
  }
  return Promise.reject(error)
}

/**
 * token 失效处理逻辑
 * @param error Error对象
 * @returns {Promise<Error>}
 */
export function invalidToken(error) {
  if (store.state.user.tokenValid) {
    store.commit('user/invalidToken')
  }
  if (process.env.VUE_APP_OFFLINE !== 'Y') {
    // 正常开发
    return loginConfirm(error)
  } else {
    // 离线开发
    return showErrorTips(error, error.response.data.msg ?? '会话已失效，请重新登录！')
  }
}

/**
 * get 请求
 * @param url{String} 请求路径
 * @param params{Object} 参数(query)
 * @returns {Promise<ResponseResult>} 请求结果
 */
export function httpGet(url, params = {}) {
  return http.get(url, {
    params: { ...params, ts: Date.now() }
  })
}

/**
 * post 请求
 * @param url{String} 请求路径
 * @param data{Object} post参数(payload)
 * @param form{Boolean} 是否使用表单方式提交数据,默认否
 * @param params{Object} 路径参数(query)
 * @param options
 * @returns {Promise<ResponseResult>} 请求结果
 */
export function httpPost(url, data = {}, form = false, params = {}, options = { header: {} }) {
  const headers = { ...(options?.header || {}) }
  setContentType(headers, form)
  return http.post(url, data, { headers, params })
}

/**
 * put 请求
 * @param url{String} 请求路径
 * @param data{Object} put参数(payload)
 * @param form{Boolean} 是否使用表单方式提交数据,默认否
 * @param params{Object} 路径参数(query)
 * @returns {Promise<ResponseResult>} 请求结果
 */
export function httpPut(url, data = {}, form = false, params = {}) {
  const headers = {}
  setContentType(headers, form)
  return http.put(url, data, { headers, params })
}

/**
 * delete 请求
 * @param url{String} 请求路径
 * @param data
 * @param params{Object} 路径参数(query)
 * @param form
 * @returns {Promise<ResponseResult>}
 */
export function httpDelete(url, data = {}, params = {}, form = true) {
  const headers = {}
  setContentType(headers, form)
  return http.delete(url, {
    params,
    data,
    headers
  })
}

/**
 * 文件上传
 * @param url {String} 上传地址
 * @param file {File} 文件
 * @param options {Object} 附加选项{data:{},headers:{},onprogress:function(progressEvent){}}
 * @returns {Promise<ResponseResult>}
 */
export function upload(url, file, options = {}) {
  const headers = { 'Content-Type': 'multipart/form-data', ...(options.headers || {}) }
  const formData = new FormData()
  // 附加额外数据
  if (options.data) {
    Object.keys(options.data).forEach(key => {
      formData.append(key, options.data[key])
    })
  }
  // 添加文件
  formData.append('file', file, file.name)
  return http.post(url, formData, {
    headers,
    timeout: 0,
    onUploadProgress: event => {
      event.percent = Math.round((event.loaded / event.total) * 100)
      options.onprogress?.(event)
    }
  })
}

/**
 * 获取默认请求头信息
 * @returns {{Authorization: string}}
 */
export const defaultHeaders = () => {
  const token = getToken()
  const headers = { Authorization: '' }
  if (token) {
    headers.Authorization = `Bearer ${token}`
  }
  headers['TENANT-ID'] = getStore('tenantId') || 1
  headers.version = 'liyue'
  return headers
}

/**
 * 获取应用静态资源
 * @param url 资源 uri
 * @returns {Promise}
 */
export const getResource = url => axios.get(url)

function getFileName(fileName) {
  if (!fileName) {
    return fileName
  }
  const arr = fileName.split(';')
  const obj = {}
  for (let i = 0; i < arr.length; i++) {
    const e = arr[i]
    const kv = e?.split('=')
    obj[kv?.[0].trim()] = decodeURIComponent(kv?.[1] ?? '')
  }
  return obj.filename
}

/**
 * 文件下载
 * @param url {String} 下载地址
 * @param options {Object} 附加选项{params:{},data:{},headers:{},onprogress:function(progressEvent){}}
 * @returns {Promise<object>}
 */
export const download = async (url, options = {}) => {
  const headers = { Accept: '*/*', ...(options.headers || {}) }
  const config = {
    headers,
    timeout: 0,
    onDownloadProgress: options.onprogress,
    params: options.params,
    responseType: 'blob'
  }
  let res
  if (options.post) {
    res = await http.post(url, options.data, config)
  } else {
    config.params = options.data
    res = await http.get(url, config)
  }
  const blob = res.data
  const fileName =
    res.headers.filename || options.fileName || getFileName(res.headers['content-disposition']) || '未命名'
  frontendDownload(fileName, blob)
  return res
}
